package com.movitech.mobile.cz.base.common.license;


import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.movitech.mobile.cz.base.common.spring.SpringContextHolder;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Base64;
import java.util.Collection;
import java.util.Objects;
import java.util.stream.Collectors;

public class LicenseUtil {

    private final static String PARAM_SIGN = "signature";
    private static Signature signature;
    private License licence;
    private Long expirTime = 0L;
    private boolean isExpired;
    private boolean validate;
    private String msg = "";
    private Object lock = new Object();
    private static final String KEY_ALGORITHM = "SHA1withRSA";
    private static final String LICENSE_EXTRA_KEY = "extra";
    public static LicenseUtil INSTANCE = new LicenseUtil();

    private LicenseUtil() {

    }

    static {
        try {
            signature = Signature.getInstance(KEY_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    public void validate() {
        try {
            getLicenseVo();
            if (!this.validate) {
                throw new RuntimeException(this.msg);
            }
            if (this.isExpired) {
                throw new RuntimeException(this.msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("系统异常Lic.");
        }
    }

    public void refresh() {
        this.licence = null;
        validate();
    }

    private License getLicenseVo() {
        if (licence == null || expirTime - System.currentTimeMillis() <= 0) {
            synchronized (lock) {
                LicenseProperties licenseProperties = SpringContextHolder.getBean(LicenseProperties.class);
                if (licence == null || expirTime - System.currentTimeMillis() <= 0) {
                    String licenceStr = FileUtil.readString(licenseProperties.licencePath, StandardCharsets.UTF_8);
                    JSONObject jsonObject = JSONUtil.parseObj(licenceStr);
                    System.out.println(jsonObject);
                    licence = jsonObject.toBean(License.class);
                    this.isEffective(licence);
                    expirTime = System.currentTimeMillis() + 2 * 60 * 60 * 1000;
                }
            }
        }
        return licence;
    }

    /**
     * 通过 cer证书获取公钥
     */
    private PublicKey getPublicKey() {
        LicenseProperties licenseProperties = SpringContextHolder.getBean(LicenseProperties.class);
        PublicKey publicKey = null;
        FileInputStream in = null;
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            in = new FileInputStream(licenseProperties.cerFilePath);
            Certificate c = cf.generateCertificate(in);
            publicKey = c.getPublicKey();
        } catch (CertificateException | FileNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return publicKey;
    }

    private boolean isEffective(License licence) {
        // 证书时间和licence时间是一致的, 如果证书过期签名校验不通过, licence肯定过期
        boolean signVerify = LicenseUtil.verify(licence, licence.getSignature(), getPublicKey());
        this.validate = signVerify;
        if (!signVerify) {
            this.msg = "License无效.sign";
            this.isExpired = true;
            return false;
        }
        if (StrUtil.isNotEmpty(licence.getMacAddress())) {
            // 校验Mac地址
            String macAddress = NetUtil.getLocalMacAddress().toLowerCase();
            String mac = licence.getMacAddress().toLowerCase();
            if (!StrUtil.contains(mac, macAddress)) {
                this.validate = false;
                System.out.println("mac:" + macAddress);
                this.msg = "License无效.mac";
                return false;
            }
        }
        String expiration = licence.getExpiration();
        if (DateUtil.compare(DateUtil.date(), DateUtil.parseDate(expiration)) >= 0) {
            // 有授权, 但是过期了
            this.validate = false;
            this.isExpired = true;
            System.out.println("expiration:" + expiration);
            this.msg = String.format("License过期了,过期日期:%s", expiration);
            return false;
        }

        return this.validate;
    }

    /**
     * 验签
     *
     * @param data
     * @param sign
     * @param publicKey
     * @return
     */
    private static boolean verify(byte[] data, byte[] sign, PublicKey publicKey) {
        try {
            signature.initVerify(publicKey);
            signature.update(data);
            return signature.verify(Base64.getDecoder().decode(sign));
        } catch (InvalidKeyException | SignatureException e) {
            throw new RuntimeException(e);
        }
    }

    private static boolean verify(Object data, String signature, PublicKey publicKey) {
        return verify(getParams(data), signature.getBytes(StandardCharsets.UTF_8), publicKey);
    }

    /**
     * 获取参数
     *
     * @param obj
     * @return
     */
    private static byte[] getParams(Object obj) {
        if (Objects.isNull(obj)) {
            return null;
        }
        // 获取非sign参数值
        JSONObject js = JSONUtil.parseObj(obj);
        js.remove(PARAM_SIGN);
        js.putOpt(LICENSE_EXTRA_KEY, js.get("licenceId").toString());
        Collection<Object> values = js.values();
        String params = values.stream().map(String::valueOf).sorted().collect(Collectors.joining());
        return params.getBytes(StandardCharsets.UTF_8);
    }

    class License {

        /**
         * 授权序列号
         */
        private String licenceId;

        /**
         * 供应商
         */
        private String vendor;
        /**
         * 过期时间
         */
        private String expiration;

        /**
         * 服务器mac地址 保证只在指定的机器运行，缺点是生成秘钥是需要知道此地址，当然可以通过ipconfig查看，然后参数传入生成licence
         */
        private String macAddress;

        /**
         * 数字签名
         */
        private String signature;

        public String getLicenceId() {
            return licenceId;
        }

        public void setLicenceId(String licenceId) {
            this.licenceId = licenceId;
        }

        public String getVendor() {
            return vendor;
        }

        public void setVendor(String vendor) {
            this.vendor = vendor;
        }

        public String getExpiration() {
            return expiration;
        }

        public void setExpiration(String expiration) {
            this.expiration = expiration;
        }

        public String getMacAddress() {
            return macAddress;
        }

        public void setMacAddress(String macAddress) {
            this.macAddress = macAddress;
        }

        public String getSignature() {
            return signature;
        }

        public void setSignature(String signature) {
            this.signature = signature;
        }
    }
}

